package git.soulbgm.security.config;

import git.soulbgm.security.filter.JwtAuthenticationTokenFilter;
import git.soulbgm.security.filter.PreLoginFilter;
import git.soulbgm.security.session.RedisSessionRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.annotation.Resource;

import static git.soulbgm.security.constant.SecurityConstants.*;

/**
 * spring security配置类
 *
 * @author SoulBGM
 * @date 2020-07-30
 */
//@EnableCaching
//@EnableRedisHttpSession
//@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthorityConfig authorityConfig;

    /**
     * 用户认证逻辑
     */
    @Resource(name = "customizeUserDetailsService")
    private UserDetailsService userDetailsService;

    /**
     * token认证过滤器
     */
    @Autowired
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    /**
     * 登录前过滤器
     */
    @Autowired
    private PreLoginFilter preLoginFilter;

    /**
     * 未登录的处理类
     */
    @Autowired
    private AuthenticationEntryPoint unauthorizedHandler;

    /**
     * 退出处理类
     */
    @Autowired
    private LogoutHandler logoutHandler;

    /**
     * 退出成功处理类
     */
    @Resource(name = "logoutSuccessHandler")
    private LogoutSuccessHandler logoutSuccessHandler;

    /**
     * 会话注册管理类
     */
    @Autowired
    private SessionRegistry sessionRegistry;

    /**
     * 访问拒绝处理类
     */
    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    /**
     * 身份验证成功处理类
     */
    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;

    /**
     * 身份验证失败处理类
     */
    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

    /**
     * 无效的会话策略类
     */
    @Autowired
    private InvalidSessionStrategy invalidSessionStrategy;

    /**
     * 会议信息过期策略类
     */
    @Autowired
    private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;

    /**
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问
     * denyAll             |   用户不能访问
     * fullyAuthenticated  |   用户完全认证可以访问（非remember-me下自动登录）
     * hasAnyAuthority     |   如果有参数，参数表示权限，则其中任何一个权限可以访问
     * hasAnyRole          |   如果有参数，参数表示角色，则其中任何一个角色可以访问
     * hasAuthority        |   如果有参数，参数表示权限，则其权限可以访问
     * hasIpAddress        |   如果有参数，参数表示IP地址，如果用户IP和参数匹配，则可以访问
     * hasRole             |   如果有参数，参数表示角色，则其角色可以访问
     * permitAll           |   用户可以任意访问
     * rememberMe          |   允许通过remember-me登录的用户访问
     * authenticated       |   用户登录后可访问
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // 跨域配置
                .cors()
                // 跨域源设置
                .configurationSource(getUrlBasedCorsConfigurationSource())
                .and()
                // 关闭csrf，否则会拦截ajax请求
                .csrf().disable()
                // 项目中用到iframe嵌入网页，然后用到springsecurity就被拦截了 浏览器报错  x-frame-options deny
                // 原因是因为springSecurty使用X-Frame-Options防止网页被Frame
                // 解决办法把x-frame-options disable即可 如下
                .headers().frameOptions().disable();
        httpSecurity
                // 1. 请求过滤的配置
                .authorizeRequests()
                // 允许匿名访问的url配置(匿名访问也就意味着获取不到用户信息,用户也无需登录)(匿名访问的url在登录之后就不能访问了 访问的话会被拒绝)
                .antMatchers(authorityConfig.getAnonymous()).anonymous()
                .antMatchers(LOGIN_ANT_PATTERNS).anonymous()
                // 允许任何用户访问的url配置
                .antMatchers(authorityConfig.getPermitAll()).permitAll()
                .antMatchers(SWAGGER_ANT_PATTERNS).permitAll()
                .antMatchers(DRUID_ANT_PATTERNS).permitAll()
                .antMatchers(ERROR_URL).permitAll()
                .antMatchers("/loginFailure", "/favicon.ico", "/sessionExpired", "/sessionInvalidation").permitAll()
                // 登录之后可以访问的url配置
                .antMatchers("/loginSuccess").authenticated()
                .antMatchers(authorityConfig.getAuthenticated()).authenticated()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().access("@ps.hasPermission(request, authentication)");

        if (authorityConfig.getLoginMode().equals(LOGIN_MODE_COOKIE)) {
            httpSecurity
                    // 2. 设置表单登录的配置
                    .formLogin()
                    // 身份验证详细信息来源
                    //.authenticationDetailsSource(authenticationDetailsSource)
                    // 处理登录请求的url
                    .loginProcessingUrl(LOGIN_PROCESSING_URL)
                    // 设置表单提交的用户名参数名称
                    .usernameParameter(USERNAME_PARAMETER)
                    // 设置表单提交的密码参数名称
                    .passwordParameter(PASSWORD_PARAMETER)
                    // 登录成功跳转的url
                    //.defaultSuccessUrl("/loginSuccess", true)
                    // 登录成功处理类
                    .successHandler(authenticationSuccessHandler)
                    // 登录失败跳转的url
                    //.failureUrl("/loginFailure")
                    // 登录失败处理类
                    .failureHandler(authenticationFailureHandler);
        }

        httpSecurity
                // 3. 认证失败的配置
                .exceptionHandling()
                // 访问拒绝跳转页面
                //.accessDeniedPage("/accessDenied")
                // 访问拒绝处理类
                .accessDeniedHandler(accessDeniedHandler)
                // 认证失败处理类
                .authenticationEntryPoint(unauthorizedHandler);


        if (authorityConfig.getLoginMode().equals(LOGIN_MODE_COOKIE)) {
            httpSecurity
                    // 4. 注销登录的配置
                    .logout()
                    // 注销登录访问的url
                    .logoutUrl("/logout")
                    // 注销处理跳转的url
                    //.logoutUrl("")
                    // 注销处理类
                    //.addLogoutHandler(logoutHandler)
                    // 注销成功跳转的url
                    //.logoutSuccessUrl("")
                    // 注销成功之后的后续处理类
                    .logoutSuccessHandler(logoutSuccessHandler)
                    .and()
                    // 清除身份信息(默认为true)
                    //.clearAuthentication(true)
                    // 使会话失效(默认为true)
                    //.invalidateHttpSession(true)
                    // 5. session管理配置
                    .sessionManagement()
                    // 设置会话认证策略类   并发会话控制身份验证策略类
                    .sessionAuthenticationStrategy(new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry))
                    // session会话失效时跳转的url
                    .invalidSessionUrl("/sessionInvalidation")
                    // session会话失效策略类
                    //.invalidSessionStrategy(invalidSessionStrategy)
                    // 会议创建策略
                    //.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                    // 最多会话(也就是说 同一用户只允许在一处登录)
                    .maximumSessions(authorityConfig.getMaximumSessions())
                    // 最大会话数阻止登录
                    .maxSessionsPreventsLogin(authorityConfig.getMaxSessionsPreventsLogin())
                    // 过期会话策略
                    //.expiredSessionStrategy()
                    // session注册
                    .sessionRegistry(sessionRegistry)
                    // session会话信息过期时跳转的url
                    .expiredUrl("/sessionExpired");
                    // session会话信息过期策略类
                    //.expiredSessionStrategy(sessionInformationExpiredStrategy);
        } else if(authorityConfig.getLoginMode().equals(LOGIN_MODE_TOKEN)){
            httpSecurity
                    // 4. 注销登录的配置
                    .logout()
                    // 注销登录访问的url
                    .logoutUrl("/logout")
                    // 注销处理跳转的url
                    //.logoutUrl("")
                    // 注销处理类
                    .addLogoutHandler(logoutHandler)
                    // 注销成功跳转的url
                    //.logoutSuccessUrl("")
                    // 注销成功之后的后续处理类
                    .logoutSuccessHandler(logoutSuccessHandler)
                    .and()
                    // 5. session管理配置
                    .sessionManagement()
                    // 会议创建策略 无状态
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
            // 添加JWT filter
            httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
            httpSecurity.addFilterBefore(authenticationTokenFilter, LogoutFilter.class);
        }
        // 在登录前加预处理过滤器
        httpSecurity.addFilterBefore(preLoginFilter, UsernamePasswordAuthenticationFilter.class);
    }

    /**
     * 跨域配置
     */
    private UrlBasedCorsConfigurationSource getUrlBasedCorsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        // 设置访问源地址
        config.addAllowedOrigin("*");
        // 设置访问源请求头
        config.addAllowedHeader("*");
        // 设置访问源请求方法
        config.addAllowedMethod("*");
        // 最终的结果是可以 在跨域请求的时候获取同一个 session
        config.setAllowCredentials(true);
        // 对接口配置跨域设置
        source.registerCorsConfiguration("/**", config);
        return source;
    }

    /**
     * 认证管理器
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 身份认证接口
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }

    /**
     * 自定义session管理
     */
    @Bean
    public SessionRegistry sessionRegistry() {
        return new RedisSessionRegistry();
    }

    /**
     * session全局监听
     */
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
}
